﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Corecalc;

namespace Sisal
{
    public class SisalGraph
    {
        public List<SisalNode> Nodes { get; set; }
        public List<SisalNode> InputEdges { get; set; }
        public List<SisalNode> OutputEdges { get; set; }

        public SisalNode OutputNode { get; set; }

        public SisalGraph() {
            Nodes = new List<SisalNode>();
            InputEdges = new List<SisalNode>();
        }

        public List<SisalNode> GetFormulaNodes() {
            return Nodes.Where(n => n.IsFormulaNode).ToList();
        }

        public IEnumerable<SisalNode> GetRCG() {
            Dictionary<FullCellAddr, SisalNode> formulaNodes = Nodes.ToDictionary(n => n.Fca, n => n);
            Dictionary<SisalNode, List<SisalNode>> outputEdges = Nodes.ToDictionary(n => n, n => new List<SisalNode>());

            foreach (SisalNode formulaNode in Nodes) {
                var nodes = formulaNode.GetGraphNodes();

                foreach (SisalNode node in nodes) {
                    if (node is FunctionCallNode) {
                        FunctionCallNode fn = node as FunctionCallNode;

                        if (formulaNodes.ContainsKey(fn.ReferredCellAdr)) {
                            SisalNode referredFormulaNode = formulaNodes[fn.ReferredCellAdr];

                            if (!outputEdges.ContainsKey(referredFormulaNode))
                                outputEdges.Add(referredFormulaNode, new List<SisalNode>());

                            if (!outputEdges[referredFormulaNode].Contains(formulaNode))
                                outputEdges[referredFormulaNode].Add(formulaNode);
                        }
                    }

                    if (node is MultipleFunctionCallNode) {
                        MultipleFunctionCallNode mfn = node as MultipleFunctionCallNode;

                        List<SisalNode> referredFormulaNodes = new List<SisalNode>();
                        mfn.ForEachCellInArea(fca => {
                            if (formulaNodes.ContainsKey(fca))
                                referredFormulaNodes.Add(Sisal.Nodes.Get(fca));
                        });

                        foreach (SisalNode referredNode in referredFormulaNodes) {
                            if (!outputEdges.ContainsKey(referredNode))
                                outputEdges.Add(referredNode, new List<SisalNode>());

                            if (!outputEdges[referredNode].Contains(formulaNode))
                                outputEdges[referredNode].Add(formulaNode);
                        }
                    }
                }
            }

            return Procedures.TopSort(formulaNodes.Values);
        }

        public void PrintGraph() {
            Console.WriteLine("- - - - - - - - - - TP Sorted Nodes - - - - - - - - - - - - ");
            IEnumerable<SisalNode> topSortedNodes = Procedures.TopSort(Nodes);
            foreach (var node in topSortedNodes) {
                Console.WriteLine(node);
            }
            Console.WriteLine();

            Console.WriteLine("- - - - - - - - - - Nodes - - - - - - - - - - - -");
            foreach (SisalNode node in Nodes) {
                Console.WriteLine("{0}", node);

                //if(node.InputEdges.Count > 0)
                //    Console.WriteLine();
                foreach (SisalNode inpNode in node.InputEdges) {
                    Console.WriteLine("\t\t\tInput from:\t {0}", inpNode);
                }

                //if (node.OutputEdges.Count > 0)
                //    Console.WriteLine();
                foreach (SisalNode outNode in node.OutputEdges) {
                    Console.WriteLine("\t\t\tOutput to:\t {0}", outNode);
                }

                Console.WriteLine();
            }

            Console.WriteLine("- - - - - - - - - - FormulaNodes - - - - - - - - - - - -");
            var formulaNodes = Nodes.Where(x => x.IsFormulaNode);
            foreach (SisalNode fn in formulaNodes) {
                Console.WriteLine("{0}[{1}, {2}]: {3}", fn.Fca.sheet, fn.Fca.ca.col, fn.Fca.ca.row, fn);
            }

            //Console.WriteLine("- - - - - - - - - - - - - - - - - ");
            //var ffffff = Nodes.Where(x => x is FormulaNode).First();
            //List<SisalNode> ns = ffffff.GetNodes();
            //foreach (SisalNode node in ns) {
            //    Console.WriteLine("{0}: {1}", node, node.ExecutionTime);
            //}
        }
    }

    public class SupportGraph
    {
        public List<FullCellAddr> Nodes { get; set; }
        public List<SupportEdge> Edges { get; set; }

        public SupportGraph() {
            Nodes = new List<FullCellAddr>();
            Edges = new List<SupportEdge>();
        }
    }

    public class SupportEdge
    {
        public FullCellAddr FromNode { get; set; }   
        public FullCellAddr ToNode { get; set; }     
    }

    public static partial class Procedures
    {
        public static SupportGraph BuildGRSupportGraph(this Workbook wb) {
            SupportGraph graph = new SupportGraph();

            for (int sc = 0; sc < wb.SheetCount; sc++) {
                Sheet sheet = wb[sc];

                for (int column = 0; column < sheet.Cols; column++) {
                    for (int row = 0; row < sheet.Rows; row++) {
                        Cell referredCell = sheet[column, row];
                        FullCellAddr referredFca = new FullCellAddr(sheet, new CellAddr(column, row));

                        if (referredCell != null) {
                        //    if (!graph.Nodes.Contains(referredFca)) {
                        //        graph.Nodes.Add(referredFca);
                        //    }

                            //referredCell.ForEachSupported((s, c, r) => {
                        //        FullCellAddr supportedFca = new FullCellAddr(s, new CellAddr(c, r));

                        //        if (!graph.Nodes.Contains(supportedFca)) {
                        //            graph.Nodes.Add(supportedFca);
                        //        }

                        //        graph.Edges.Add(new SupportEdge {
                        //            FromNode = referredFca,
                        //            ToNode = supportedFca
                        //        });
                            //});
                        }
                    }
                }
            }

            return graph;
        }

        public static SisalGraph Expand(this SupportGraph g) {
            SisalGraph sisalGraph = new SisalGraph();
            Dictionary<FullCellAddr, SisalNode> seenBefore = new Dictionary<FullCellAddr, SisalNode>();

            foreach (SupportEdge edge in g.Edges) {
                //From node (FromNode supports ToNode)
                Sheet fromSheet = edge.FromNode.sheet;
                int fromCol = edge.FromNode.ca.col;
                int fromRow = edge.FromNode.ca.row;
                Cell cell = fromSheet[fromCol, fromRow];

                if (!(cell is ConstCell)) {
                    SisalNode fromNode = cell.Expand(fromSheet, fromCol, fromRow);
                    FullCellAddr fromFca = new FullCellAddr(fromSheet, edge.FromNode.ca);

                    if (!seenBefore.ContainsKey(fromFca)) {
                        seenBefore.Add(fromFca, fromNode);
                        sisalGraph.Nodes.Add(fromNode);
                    }
                }

                //To node (ToNode refers to FromNode)
                Sheet toSheet = edge.ToNode.sheet;
                int toCol = edge.ToNode.ca.col;
                int toRow = edge.ToNode.ca.row;
                SisalNode toNode = toSheet[toCol, toRow].Expand(toSheet, toCol, toRow);
                FullCellAddr toFca = new FullCellAddr(toSheet, edge.ToNode.ca);

                if (!seenBefore.ContainsKey(toFca)) {
                    seenBefore.Add(toFca, toNode);
                    sisalGraph.Nodes.Add(toNode);
                }
            }

            return sisalGraph;
        }
    }
}
